package org.chorus;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;

import org.chorus.Modular.Direction;

import jwo.landserf.structure.RasterMap;

import processing.core.PApplet;

// *********************************************************************************************
/** Handles loading and customising a properties file that contains details of the files to 
 *  process and actions to take with them.
 *  @author Jo Wood, giCentre.
 *  @version 1.3.2, 11th March, 2013.
 */ 
// *********************************************************************************************

/* This file is part of the Chorus river bed analysis software package. Chorus is free software:
 * you can redistribute it and/or modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 * 
 * Chorus is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this
 * source code (see COPYING included with this source code). If not, see 
 * http://www.gnu.org/licenses/.
 */

public class PropertyHandler 
{
	// ---------------------------- Object variables -------------------------------
	
	private Properties props;
	private PApplet parent;
	private boolean propsFileFound;
	
	// Property key labels.
	private static final String BASE_DIR      = "baseDir";
	private static final String DETREND_FILES = "detrendFiles";
	private static final String MODULE        = "module";
	private static final String TITLE         = "title";
	private static final String UNITS         = "units";
	private static final String DOWNSTREAM    = "downstream";
	
	// Module type names
	private static final String DEM_DISPLAY		= "demDisplay";
	private static final String INCLINATION		= "inclination";
	private static final String INCLINATION_2D	= "inclination2d";
	private static final String PDF         	= "pdf";
	private static final String VARIOGRAM		= "variogram";
	private static final String VARIOGRAM_2D	= "variogram2d";
	
	// Valid directions
	private static final String LEFT_TO_RIGHT	= "leftToRight";
	private static final String TOP_TO_BOTTOM	= "topToBottom";
	
    // ------------------------------ Constructor ----------------------------------
				
	/** Creates a set of properties from the given file. It is assumed that the file name
	 *  is expressed relative to the 'data' directory of this application.
	 *  @param fileName Name of file containing the user defined properties.
	 *  @param parent Parent sketch relative to which the properties file will be found.
	 */
	public PropertyHandler(String fileName, PApplet parent)
	{
		this.parent = parent;
		
		// Get the default properties first.
		props = new Properties();
		props.setProperty(BASE_DIR, "./data/");
		props.setProperty(TITLE," ");
		props.setProperty(UNITS,"mm");
		props.setProperty(DOWNSTREAM,LEFT_TO_RIGHT);
		propsFileFound = false;
				
		// Attempt to load the properties file from the data directory
		try
		{
			InputStream inStream  = parent.createInput(fileName);
			if (inStream == null)
			{
				System.err.println("Warning: Properties file '"+fileName+"' not found.");
			}
			else
			{
				props.load(inStream);
				System.out.println("Properties loaded from "+fileName);
				propsFileFound = true;
			}
		}
		catch (IOException e)
		{
			System.err.println("Problem reading properties file: "+e);
		}
	}
	
	// -------------------------------- Methods ------------------------------------
	
	/** Reports whether or not a properties file has been found and loaded. Note that in some
	 *  circumstances, failing to load a properties file may not be a problem if the default
	 *  properties are suitable.
	 *  @return True if the file has been loaded successfully, otherwise false.
	 */
	public boolean foundPropertiesFile()
	{
		return propsFileFound;
	}
	
	/** Reports the path of the base directory as defined in the properties file.
	 *  @return Base directory from which file names may be expressed.
	 */
	public String getBaseDir()
	{
		return props.getProperty(BASE_DIR).trim();
	}
	
	/** Reports the display title as defined in the properties file.
	 *  @return Title of the report.
	 */
	public String getTitle()
	{
		return props.getProperty(TITLE).trim();
	}
	
	/** Reports the planimetric units as defined in the properties file.
	 *  @return Unit labels to display on chart axes.
	 */
	public String getUnits()
	{
		return props.getProperty(UNITS).trim();
	}
	
	/** Reports the raster orientation representing the downstream direction.
	 *  @return Downstream direction
	 */
	public Direction getDownstream()
	{
		if (props.getProperty(DOWNSTREAM).trim().equalsIgnoreCase(TOP_TO_BOTTOM))
		{
			return Modular.Direction.TOP_TO_BOTTOM;
		}
		return Modular.Direction.LEFT_TO_RIGHT;
	}
	
	/** Reports a list of file names representing those to read into the Chorus detrending application.
	 *  @return List of file names. All are expressed relative to the baseDir property.
	 */
	public String[] getDetrendNames()
	{
		String files = props.getProperty(DETREND_FILES);
		
		if ((files == null) ||(files.trim().length() == 0))
		{
			return new String[0];
		}
		
		String[] fileList = PApplet.split(files, ",");
		for (int i=0; i<fileList.length; i++)
		{
			fileList[i] = fileList[i].trim();
		}
		return fileList;
	}
	
	/** Provides a collection of modules as defined in the properties file. Each module can be
	 *  associated with a set of file names representing files to be processed by the module.
	 *  @return Map of modules and associated file names.
	 */
	public Map<Modular, String[]> getModules()
	{
		int moduleNum = 0;
		TreeMap<Modular, String[]> moduleList = new TreeMap<Modular, String[]>();
		String module = props.getProperty(MODULE+moduleNum);
		
		while (module != null)
		{
			
			String[] tokens = PApplet.split(module,",");
			String moduleName = tokens[0].trim();
		
			int colourOffset  =-1;
			String param = "";
						
			// Look for optional parameter
			if (moduleName.contains("("))
			{
				String[] moduleTokens = PApplet.splitTokens(moduleName,"()");

				if (moduleTokens.length == 1)		// Empty brackets
				{
					moduleName = moduleTokens[0].trim();
				}
				else
				{
					param = moduleTokens[1].trim();
					if (moduleTokens.length >2)
					{
						moduleName = moduleTokens[0].trim()+moduleTokens[2].trim();
					}
					else
					{
						moduleName = moduleTokens[0].trim();
					}
				}
			}
			
			// Look for optional colour offset.
			if (moduleName.contains(":"))
			{
				String[] moduleTokens = PApplet.split(moduleName,":");
				
				try
				{
					colourOffset = Integer.parseInt(moduleTokens[1]);
					moduleName = moduleTokens[0].trim();
				}
				catch (NumberFormatException e)
				{
					System.err.println("Problem extracting colour offset from "+moduleName);
				}
			}
			
			Modular mod = null;
			if (moduleName.equalsIgnoreCase(DEM_DISPLAY))
			{
				if (param.length()>0)
				{
					// We have a DEM parameter.
					if (param.equalsIgnoreCase("aspect"))
					{
						mod = new DEMDisplay(RasterMap.ASPECT);
					}
					else if (param.equalsIgnoreCase("elevation"))
					{
						mod = new DEMDisplay(RasterMap.ELEV);
					}
					else if (param.equalsIgnoreCase("feature"))
					{
						mod = new DEMDisplay(RasterMap.FEATURE);
					}
					else if (param.equalsIgnoreCase("planc"))
					{
						mod = new DEMDisplay(RasterMap.PLANC);
					}
					else if (param.equalsIgnoreCase("profc"))
					{
						mod = new DEMDisplay(RasterMap.PROFC);
					}
					else if (param.equalsIgnoreCase("slope"))
					{
						mod = new DEMDisplay(RasterMap.SLOPE);
					}
					else
					{
						System.err.println("Warning unknown parameter '"+param+"' provided to "+moduleName);
						mod = new DEMDisplay();
					}
				}
				else
				{
					mod = new DEMDisplay();
				}
			}
			else if (moduleName.equalsIgnoreCase(PDF))
			{
				mod = new PDFGrapher();
			}
			else if (moduleName.equalsIgnoreCase(VARIOGRAM))
			{
				if (param.length()==1)
				{
					// We have a single variogram parameter.
					if (param.equalsIgnoreCase("all"))
					{
						mod = new Variogram(Direction.ALL,getDownstream());
					}
					else if (param.equalsIgnoreCase("downstream"))
					{
						mod = new Variogram(Direction.DOWNSTREAM,getDownstream());
					}
					else if (param.equalsIgnoreCase("crossstream"))
					{
						mod = new Variogram(Direction.CROSSSTREAM,getDownstream());
					}
					else
					{
						// We may have a single numeric parameter representing maximum lag.
						try
						{
							float maxLag = Float.parseFloat(param);
							mod = new Variogram(maxLag);
						}
						catch (NumberFormatException e)
						{
							System.err.println("Warning unknown parameter '"+param+"' provided to "+moduleName);
							mod = new Variogram();
						}
					}
				}
				else if (param.length()>1)
				{
					// We have at least two variogram parameters.
					String maxLagText ="0";
					Direction dir = Direction.ALL;
					if (param.toLowerCase().contains("all"))
					{
						maxLagText = param.toLowerCase().replace("all","").trim();
						dir = Direction.ALL;
					}
					else if (param.toLowerCase().contains("downstream"))
					{
						maxLagText = param.toLowerCase().replace("downstream","").trim();
						dir = Direction.DOWNSTREAM;
					}
					else if (param.toLowerCase().contains("crossstream"))
					{
						maxLagText = param.toLowerCase().replace("crossstream","").trim();
						dir = Direction.CROSSSTREAM;
					}
					else
					{
						System.err.println("Warning unknown parameter '"+param+"' provided to "+moduleName);
						mod = new Variogram();
					}
					
					try
					{
						if (maxLagText.length()>0)
						{
							float maxLag = Float.parseFloat(maxLagText);
							mod = new Variogram(dir,getDownstream(),maxLag);
						}
						else
						{
							mod = new Variogram(dir,getDownstream());
						}
					}
					catch (NumberFormatException e)
					{
						System.err.println("Warning unknown parameter '"+maxLagText+"' provided to "+moduleName);
					}
				}
				else
				{
					mod = new Variogram();
				}
			}
			else if (moduleName.equalsIgnoreCase(VARIOGRAM_2D))
			{
				if (param.length()>0)
				{
					try
					{
						float maxLag = Float.parseFloat(param);
						mod = new Variogram2D(maxLag);
					}
					catch (NumberFormatException e)
					{
						System.err.println("Warning: Cannot extract maximum lag from 2D variogram: "+param);
					}
				}
				else
				{
					mod = new Variogram2D();
				}
			}
			else if (moduleName.equalsIgnoreCase(INCLINATION))
			{
				if (param.length()==1)
				{
					// We have a single variogram parameter.
					if (param.equalsIgnoreCase("downstream"))
					{
						mod = new InclinationChart(Direction.DOWNSTREAM,getDownstream());
					}
					else if (param.equalsIgnoreCase("crossstream"))
					{
						mod = new InclinationChart(Direction.CROSSSTREAM,getDownstream());
					}
					else
					{
						// We may have a single numeric parameter representing maximum lag.
						try
						{
							float maxLag = Float.parseFloat(param);
							mod = new InclinationChart(maxLag);
						}
						catch (NumberFormatException e)
						{
							System.err.println("Warning unknown parameter '"+param+"' provided to "+moduleName);
							mod = new InclinationChart();
						}
					}
				}
				else if (param.length()>1)
				{
					// We have at least two inclination chart parameters.
					String maxLagText ="0";
					Direction dir = Direction.DOWNSTREAM;
					if (param.toLowerCase().contains("downstream"))
					{
						maxLagText = param.toLowerCase().replace("downstream","").trim();
						dir = Direction.DOWNSTREAM;
					}
					else if (param.toLowerCase().contains("crossstream"))
					{
						maxLagText = param.toLowerCase().replace("crossstream","").trim();
						dir = Direction.CROSSSTREAM;
					}
					else
					{
						System.err.println("Warning unknown parameter '"+param+"' provided to "+moduleName);
						mod = new InclinationChart();
					}
					
					try
					{
						if (maxLagText.length()>0)
						{
							float maxLag = Float.parseFloat(maxLagText);
							mod = new InclinationChart(dir,getDownstream(),maxLag);
						}
						else
						{
							mod = new InclinationChart(dir,getDownstream());
						}
					}
					catch (NumberFormatException e)
					{
						System.err.println("Warning unknown parameter '"+maxLagText+"' provided to "+moduleName);
					}
				}
				else
				{
					mod = new Variogram();
				}	
			}
			else if (moduleName.equalsIgnoreCase(INCLINATION_2D))
			{
				mod = new InclinationChart2D();
			}
			
			if (mod != null)
			{
				mod.setup(parent);
				
				String[] fileList = new String[tokens.length-1];	
				for (int i=1; i<tokens.length; i++)
				{
					fileList[i-1] = tokens[i].trim();
				}
				mod.setOrder(moduleNum);
				if (colourOffset > 0)
				{
					mod.setColourOffset(colourOffset);
				}
				moduleList.put(mod,fileList);
			}
			moduleNum++;
			module = props.getProperty(MODULE+moduleNum);
		}
		return moduleList;
	}
	
	
	/** Displays a list of the usable properties. Useful for debugging. 
	 */
	void listProps()
	{
		props.list(System.out);
	}
	
}
